﻿using System;
using System.Data;
using System.IO;
using System.Collections.Generic;
using System.Threading;
using Csu.Modsim.ModsimModel;
using ASquared;
using ASquared.SymbolicMath;
using ASquared.ModelOptimization;
using Microsoft.SolverFoundation.Services;
using Microsoft.SolverFoundation.Common;
using System.Text;

namespace iwpm.optimization
{
    public class TwoBusSystem : HydroTargetSeeker
    {
        public static int poweriter = 50;

        private Csu.Modsim.ModsimModel.Model _model;
        private HydropowerTarget[] _left, _right, _all;
        private TimeSeries _leftPowerDevs, _rightPowerDevs;
        private double _maxPowerFlow, _calcPowerFlow, _hydroWeight; // MW
        private double _totalLoad;
        private PowerSystemBase _powerFxn;
        private ConditionalGradientSolver _solver;
        private int _prevModelIter = int.MaxValue;
        private double[][,] _origTargets;
        private int _pfIndexRight, _pfIndexLeft;
        private string _logFile;
        private double _powerTolerance = 0.0001;
        private int _powerMaxIters = 50;
        private double _alpha = -1;
        private int[] _leftindices, _rightindices, _allI;
        private double[] _weights;
        private bool _useAugmented = false;
        private PowerSystemSolverMethod _powerSolverMethod = PowerSystemSolverMethod.Default;
        private bool _powerInParallel = false;
        private bool _powerJustAtFirst = false; // true;
        private Thread _powerThread;
        private Decision _swingImport, _swingExport, _slackLeft, _slackRight;
        private Node _gcl;
        private Symbol _gcl_targ, _gcl_excess, _gcl_evap;
        private Symbol _gcl_spill, _chj_spill;

        // Properties
        public double[][,] OriginalTargets { get { return _origTargets; } set { _origTargets = value; } }
        public int NumOfTargets { get { return _left.Length + _right.Length; } }
        public HydropowerTarget[] LeftTargets { get { return _left; } set { _left = value; } }
        public HydropowerTarget[] RightTargets { get { return _right; } set { _right = value; } }
        /// <summary>Gets the calculated power flow from left to right of the two-bus system (the swing bus is on the right side).</summary>
        public double CalculatedPowerFlow { get { return _calcPowerFlow; } }
        public double MaxPowerFlow { get { return _maxPowerFlow; } set { _maxPowerFlow = value; } }
        public double TotalLoad { get { return _totalLoad; } }
        public double LeftLoad { get { return _powerFxn.getOrigTarget(_leftindices); } } // return HydroTargetUpdater.SumEnergy(_left, true, false); } }
        public double RightLoad { get { return _powerFxn.getOrigTarget(_rightindices); } } // return HydroTargetUpdater.SumEnergy(_right, true, false); } }
        public double LeftGen { get { return HydroTargetUpdater.SumEnergy(_left, false, false); } }
        public double RightGen { get { return HydroTargetUpdater.SumEnergy(_right, false, false); } }
        public bool MinimizeSwing { get; set; }
        public double SwingBusPower
        {
            get
            {

                if (_powerSolverMethod == PowerSystemSolverMethod.Simplex)
                    return _swingImport.ToDouble() - _swingExport.ToDouble();
                else
                    return _slackLeft.ToDouble() + _slackRight.ToDouble();
            }
        }
        public double SlackLeft { get { if (_powerSolverMethod == PowerSystemSolverMethod.Default) return _slackLeft.ToDouble(); return 0.0;  } }
        public double SlackRight { get { if (_powerSolverMethod == PowerSystemSolverMethod.Default) return _slackRight.ToDouble(); return 0.0; } }
        public double TimestepHours { get { return _model.timeStep.ToTimeSpan(_model.mInfo.CurrentBegOfPeriodDate).TotalHours; } }
        public string PowerLogFile { get { return _logFile; } set { _logFile = value; } }
        public double PowerTolerance { get { return _powerTolerance; } set { _powerTolerance = value; } }
        public int PowerMaxIterations { get { return _powerMaxIters; } set { _powerMaxIters = value; } }
        public ConditionalGradientSolver PowerSolver { get { return _solver; } set { _solver = value; } }
        public LagrangeMultiplierMatrix Mu { get { return _powerFxn.Mu; } }
        public double Alpha { get { return _alpha; } set { _alpha = value; } }
        public PowerSystemSolverMethod PowerSolverMethod { get { return _powerSolverMethod; } set { _powerSolverMethod = value; } }
        public bool UseAugmentedLagrangian { get { return _useAugmented; } set { _useAugmented = value; } }

        public double LeftPowerDeviation
        {
            get
            {
                if (_leftPowerDevs == null)
                    return 0.0;
                return (double)_leftPowerDevs.dataTable.Rows[_model.mInfo.CurrentModelTimeStepIndex][1];
            }
        }

        public double RightPowerDeviation
        {
            get
            {
                if (_rightPowerDevs == null)
                    return 0.0;
                return (double)_rightPowerDevs.dataTable.Rows[_model.mInfo.CurrentModelTimeStepIndex][1];
            }
        }

        public override Symbol fxn
        {
            get
            {
                // Update iteration 
                if (_prevModelIter > _model.mInfo.Iteration)
                {
                    _totalLoad = HydroTargetUpdater.SumEnergy(_left, true, false) + HydroTargetUpdater.SumEnergy(_right, true, false) - this.LeftPowerDeviation - this.RightPowerDeviation;
                    if (_powerJustAtFirst)
                        this.SolvePower();
                }
                if (!_powerJustAtFirst)
                    this.SolvePower();

                _prevModelIter = _model.mInfo.Iteration;

                // calculate the power flow
                double s_l = 0.0;
                if (_model.mInfo.Iteration >= poweriter)
                    s_l = _slackLeft.ToDouble();
                _calcPowerFlow = (s_l + HydroTargetUpdater.SumEnergy(_left, false, false) + this.LeftPowerDeviation - this.LeftLoad) / this.TimestepHours;

                // Set the objective function (sum of squared difference between energy produced and original energy targets (unchanged))
                Symbol f;
                if (_model.hydro.IterativeTechnique != IterativeSolutionTechnique.Nothing && _model.hydro.IterativeTechnique != IterativeSolutionTechnique.SeparateHydroLinks)
                    return base.fxn;
                else
                    switch (_powerFxn.scaleoption)
                    {
                        case PowerSystemBase.ScaleType.SqrDiff:
                            f = _powerFxn.Weight / 100.0 * base.TargetCalculator.TotalSqrEnergyDiff(_powerFxn.CurrentOriginalTargets)
                                + 100 * _powerFxn.Weight * base.RoutingCompatibility; // theFxn;
                            break;
                        case PowerSystemBase.ScaleType.SqrDiffOverCap:
                            f = _powerFxn.Weight / 100.0 * base.TargetCalculator.TotalSqrEnergyDiffNrml(_powerFxn.CurrentOriginalTargets)
                                + 100 * _powerFxn.Weight * base.RoutingCompatibility; // theFxn;
                            break;
                        case PowerSystemBase.ScaleType.InvNSCE:
                            Symbol top = 0.0;
                            double bottom = 0.0;
                            for (int hunit = 0; hunit < _all.Length; hunit++)
                            {
                                top += ((base.TargetCalculator.Energy(hunit) - _powerFxn.CurrentOriginalTargets[hunit]) ^ 2);
                                bottom += Math.Pow(_powerFxn.CurrentOriginalTargets[hunit] - _powerFxn.meantargets[hunit], 2);
                            }
                            f = _powerFxn.Weight / 100.0 * top / bottom + 100 * _powerFxn.Weight * base.RoutingCompatibility;
                            break;
                        default:
                            throw new Exception("the scale type " + _powerFxn.scaleoption.ToString()
                                + " is not implemented in TwoBusSystem.cs");
                    }

                // Add flood control term
                double target = _gcl.mnInfo.targetcontent[_model.mInfo.CurrentModelTimeStepIndex, 0];
                if (_gcl.mnInfo.stend > target)
                {
                    Symbol stend;
                    if (_gcl.mnInfo.evpt > 0)
                        stend = _gcl_targ + _gcl_excess + _gcl_evap - _gcl.mnInfo.evpt;
                    else
                        stend = _gcl_targ + _gcl_excess;
                    f += _powerFxn.Weight * 10.0 * (Sym.Pow(_gcl_excess, 2) + Sym.Pow(_gcl_targ - target, 2));
                    f += this.SpillWeight * Sym.Pow(_chj_spill, 2);
                }
                else
                {
                    f += base.SpillPenaltyFunction;
                }

                // Add Lagrangian multiplier terms
                if (_useAugmented)
                {
                    // add augmented lagrangian portion to the function 
                    for (int i = 0; i < _all.Length; i++)
                    {
                        f += Mu[i] * (base.TargetCalculator.EnergyDiff(i)); // / _powerFxn.Capacities[i]);
                        f += Mu.Alpha / 2.0 * ((base.TargetCalculator.EnergyDiff(i)) ^ 2); // / _powerFxn.Capacities[i]) ^ 2);
                    }
                }
                else
                {
                    // add lagrangian portions to the function
                    for (int i = 0; i < _all.Length; i++)
                        f += Mu[i] * base.TargetCalculator.Energy(i); // / _powerFxn.Capacities[i];
                }
                f = this.AddModsimCosts(f);
                return f;
            }
        }

        // Constructor
        public TwoBusSystem(Csu.Modsim.ModsimModel.Model model, HydropowerTarget[] left, HydropowerTarget[] right, string leftPowerDevsFile, string rightPowerDevsFile, double hydroWeight, double maxPowerFlow)
            : base(model, hydroWeight)
        {
            _model = model;
            _left = left;
            _right = right;
            if (File.Exists(leftPowerDevsFile))
                _leftPowerDevs = HydroTargetUpdater.ReadPowerFile(leftPowerDevsFile, model);
            else
                _leftPowerDevs = null;
            if (File.Exists(leftPowerDevsFile))
                _rightPowerDevs = HydroTargetUpdater.ReadPowerFile(rightPowerDevsFile, model);
            else
                _rightPowerDevs = null;
            _maxPowerFlow = maxPowerFlow;
            _hydroWeight = hydroWeight;
        }

        public TwoBusSystem(Csu.Modsim.ModsimModel.Model model, TwoBusSystemInputs inputs)
            : base(model, inputs.HydroObjectiveWeight)
        {
            _model = model;
            _left = inputs.LeftTargets;
            _right = inputs.RightTargets;
            _leftPowerDevs = inputs.LeftPowerDeviations;
            _rightPowerDevs = inputs.RightPowerDeviations;
            _maxPowerFlow = inputs.MaximumPowerFlow;
            _hydroWeight = inputs.HydroObjectiveWeight;
        }

        // Methods 
        public override void Initialize()
        {
            base.Initialize();

            // fill all the hydropower targets 
            _all = new HydropowerTarget[NumOfTargets];
            int i;
            _leftindices = new int[_left.Length];
            _rightindices = new int[_right.Length];
            _weights = new double[_left.Length];
            _allI = new int[NumOfTargets];
            for (i = 0; i < _left.Length; i++)
            {
                _all[i] = _left[i];
                _leftindices[i] = i;
                _weights[i] = 1.0 / this.TimestepHours;
                _allI[i] = i;
            }
            for (; i < NumOfTargets; i++)
            {
                _all[i] = _right[i - _left.Length];
                _allI[i] = i;
                _rightindices[i - _left.Length] = i;
            }

            // update the hydropower calculator
            base.TargetCalculator = new HydroTargetCalculator(_model, _all);
            base.TargetCalculator.Initialize();

            // save the original hydropower targets
            _origTargets = new double[_all.Length][,];
            for (i = 0; i < _all.Length; i++)
            {
                _origTargets[i] = (double[,])_all[i].EnergyTargets.Clone();
                foreach (Link link in _all[i].FlowLinks)
                {
                    base.variableMask[link.number - 1] = 1;
                }
            }
            // build the power system model (adding the power flow constraint)
            _powerFxn = new PowerSystemBase(_model, _all);
            _powerFxn.MinimizeSwing = this.MinimizeSwing;
            _powerFxn.Initialized += new EventHandler(_powerFxn_Initialized);
            _powerFxn.Weight = _hydroWeight;
            _powerFxn.SolverMethod = _powerSolverMethod;
            base.SpillWeight = _hydroWeight;

            _solver = new ConditionalGradientSolver(_powerFxn);
            //_solver.LogFile = _logFile; // might be an expensive operation
            _solver.MaxIterations = _powerMaxIters;
            _solver.Tolerance = _powerTolerance;
            _solver.LineSearchTolerance = 0.1;

            _gcl = _model.FindNode("GCL");
            _gcl_targ = _add_q(_gcl.mnInfo.targetLink);
            _gcl_excess = _add_q(_gcl.mnInfo.excessStoLink);
            _gcl_evap = _add_q(_gcl.mnInfo.evapLink);
            _gcl_spill = _add_q(_model.FindLink("GCL_Spill"));
            _chj_spill = _add_q(_model.FindLink("CHJ_Spill"));
        }

        Symbol _add_q(Link l)
        {
            this.variableMask[l.number - 1] = 1;
            return this.SymbolVariables[l.number - 1];
        }

        void _powerFxn_Initialized(object sender, EventArgs e)
        {
            // Set the master problem type 
            Mu.UseAugmented = _useAugmented;
            Mu.Alpha = _hydroWeight * 100;
            if (_useAugmented)
                _powerFxn.ObjType = PowerSystemBase.ObjectiveType.AugmentedLagrangian;
            else
                _powerFxn.ObjType = PowerSystemBase.ObjectiveType.Lagrangian;

            // Initialize decision variables 
            Term P = 0.0;
            List<Decision> Ps = new List<Decision>(_powerFxn.SolverModel.Decisions);

            // Calculate the floating-point variables on the left bus (used in calculations)
            double powerFlowLessP = (this.LeftPowerDeviation - this.LeftLoad) / this.TimestepHours;

            switch (_powerSolverMethod)
            {
                case PowerSystemSolverMethod.ConditionalGradient:

                    _pfIndexRight = _powerFxn.AddConstraint(_leftindices, _weights, _maxPowerFlow, ConstraintType.LeftLessThanRight);
                    _pfIndexLeft = _powerFxn.AddConstraint(_leftindices, _weights, -_maxPowerFlow, ConstraintType.LeftGreaterThanRight);

                    // power flow capped at max power flow via constraint in power system solver
                    _powerFxn.b[_pfIndexRight] = _maxPowerFlow - powerFlowLessP;
                    _powerFxn.b[_pfIndexLeft] = -_maxPowerFlow - powerFlowLessP;

                    break;
                case PowerSystemSolverMethod.Simplex:
                    // AQD: I don't think we want to subtract the swing here.
                    _swingImport = Ps.Find(dec => dec.Name.Equals("swingImport"));
                    //P -= _swingImport / TimestepHours; 
                    _swingExport = Ps.Find(dec => dec.Name.Equals("swingExport"));
                    //P += _swingExport / TimestepHours; 

                    // Add the power flow constraints
                    if (_maxPowerFlow > 0)
                    {
                        // Calculate the left hand side of the inequalities (decision variables) 
                        for (int i = 0; i < _leftindices.Length; i++)
                            P += Ps[_leftindices[i]] / TimestepHours;
                        _powerFxn.SolverModel.AddConstraints("Powerflow",
                            P <= _maxPowerFlow - powerFlowLessP,
                            P >= -_maxPowerFlow - powerFlowLessP);
                    }
                    break;
                case PowerSystemSolverMethod.Default:
                    // AQD: I don't think we want to substract the swing here.
                    _slackLeft = Ps.Find(dec => dec.Name.Equals("slack_left"));
                    _slackRight = Ps.Find(dec => dec.Name.Equals("slack_right"));
                    // P -= _swing / TimestepHours; 

                    // Add the power flow constraints
                    if (_maxPowerFlow > 0)
                    {
                        // Calculate the left hand side of the inequalities (decision variables) 
                        for (int i = 0; i < _leftindices.Length; i++)
                            P += Ps[_leftindices[i]] / TimestepHours;
                        P += _slackLeft / TimestepHours;
                        _powerFxn.SolverModel.AddConstraints("Powerflow",
                        P <= _maxPowerFlow - powerFlowLessP,
                        P >= -_maxPowerFlow - powerFlowLessP);
                    }
                    break;
                default:
                    throw new Exception("Undefined power system solver method: " + _powerSolverMethod.ToString());
            }

            // Calculate the double value of the current power flow 
            _calcPowerFlow = (HydroTargetUpdater.SumEnergy(_left, true, false) + this.LeftPowerDeviation - this.LeftLoad) / this.TimestepHours;
        }

        private double sumEnergyDiff(bool left, bool takeAbsolute)
        {
            return HydroTargetUpdater.SumEnergyDiff(left ? _left : _right, takeAbsolute);
        }

        private double[] getOrigTargets()
        {
            double[] vals = new double[_origTargets.Length];
            for (int i = 0; i < _origTargets.Length; i++)
                vals[i] = _origTargets[i][_model.mInfo.CurrentModelTimeStepIndex, 0];
            return vals;
        }

        public void SolvePower()
        {
            // solve power system
            try
            {
                _powerFxn.Load = _totalLoad;
                switch (_powerSolverMethod)
                {
                    case PowerSystemSolverMethod.ConditionalGradient:
                        if (_powerInParallel)
                            _solver.SolveOnNewThread();
                        else
                            _solver.Solve();
                        break;
                    case PowerSystemSolverMethod.Simplex:
                        _powerFxn.Initialize();
                        if (_model.mInfo.Iteration < poweriter)
                            break;
                        if (_powerInParallel)
                        {
                            _powerThread = new Thread(() => _powerFxn.Solve(true));
                            _powerThread.Start(true);
                        }
                        else
                            _powerFxn.Solve(true);
                        break;

                    case PowerSystemSolverMethod.Default:
                        _powerFxn.Initialize();
                        if (_model.mInfo.Iteration < poweriter)
                            break;
                        if (_powerInParallel)
                        {
                            _powerThread = new Thread(() => _powerFxn.Solve(false));
                            _powerThread.Start();
                        }
                        else
                        {
                            _powerFxn.Solve(false);
                        }
                        break;
                    default:
                        throw new Exception("The power solver method specified (" + _powerSolverMethod.ToString() + ") is not implemented in the TwoBusSystem.cs file");

                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error in power solver: " + ex.ToString());
            }
        }

        public void WaitOnPower()
        {
            if (_powerSolverMethod == PowerSystemSolverMethod.ConditionalGradient)
                _solver.WaitUntilSolved();
            else if (_powerThread != null)
                _powerThread.Join();
        }

        public Matrix ConstraintValue()
        {
            return _powerFxn.ConstraintValue();
        }

        public bool UpdateMultipliers()
        {
            if (_model.mInfo.Iteration < poweriter)
                return false;
            this.WaitOnPower();
            bool convg = _powerFxn.UpdateMultipliers();
            return convg;
        }

#if DEBUG
        private StreamWriter _up_sw = null;
        private StringBuilder _constraints = new StringBuilder();
        private StringBuilder _equations = new StringBuilder();
        private StringBuilder _models = new StringBuilder();
        private bool _gams_syntax = false;
        private Dictionary<String, String> _dict = new Dictionary<string, string>();
        private List<Symbol> _fxns = new List<Symbol>();
        private List<double> _lastLos = new List<double>();
        private List<double> _lastHis = new List<double>();
#endif
        public Symbol AddModsimCosts(Symbol f)
        {
            Symbol extra = 0.0;
            int num;
            Link l;
            String variable;
            List<String> vars = f.Variables();
            for (int i = 0; i < this.variables.Length; i++)
            {
                variable = this.variables[i];
                if (!vars.Contains(variable))
                {
                    if (!int.TryParse(variable.Replace("q_", ""), out num))
                        _model.FireOnError("Could not parse {0} to a number!");
                    l = _model.Links_All[num - 1];
                    if (l.mlInfo.cost != 0.0)
                        extra += l.mlInfo.cost * this.SymbolVariables[i];
                }
            }
#if DEBUG
            if (_model.mInfo.CurrentModelTimeStepIndex == 0)
            {
                if (_up_sw == null)
                {
                    string leftside = _gams_syntax ? "q('" : "";
                    string rightside = _gams_syntax ? "')" : "";
                    string equalsign = _gams_syntax ? " =e= " : " == ";
                    string eqnameleft = _gams_syntax ? "eq_" : "";
                    string eqdots = _gams_syntax ? ".." : "";
                    string lineEnd = _gams_syntax ? ";" : "";

                    // open up the output stream writer
                    _up_sw = new StreamWriter("upperbounds.txt");
                    _up_sw.Write("Iteration\tObjective_Function");
                    foreach (String var in this.variables)
                        _up_sw.Write("\tcost_" + var);
                    foreach (String var in this.variables)
                        _up_sw.Write("\tlo_" + var); 
                    foreach (String var in this.variables) 
                        _up_sw.Write("\thi_" + var);
                    _up_sw.WriteLine();

                    // beginning of equations and models strings 
                    if (_gams_syntax)
                    {
                        _equations.Append("equations" + _up_sw.NewLine);
                        _models.Append(string.Format("model full{0}/   eq_lo,{0}    eq_hi,{0}    eq_obj", _up_sw.NewLine));
                    }

                    // get a list of all pertinant constraints
                    foreach (Node n in _model.Nodes_All)
                    {
                        // build constraint equations 
                        _constraints.Clear();
                        if (n.InflowLinks != null)
                            _constraints.Append(String.Join(" + ", Array.ConvertAll<Link, String>(n.InflowLinks.ToArray(), li => leftside + "q_" + li.number.ToString() + rightside)));
                        else
                            _constraints.Append("0");
                        //if (n.mnInfo.inflow.Length > 0)
                        //    _constraints.Append(" + " + (n.mnInfo.inflow[_model.mInfo.CurrentModelTimeStepIndex,0]/_model.ScaleFactor).ToString());
                        _constraints.Append(equalsign);
                        if (n.OutflowLinks != null)
                            _constraints.Append(String.Join(" + ", Array.ConvertAll<Link, String>(n.OutflowLinks.ToArray(), li => leftside + "q_" + li.number.ToString() + rightside)));
                        else
                            _constraints.Append("0");
                        _constraints.Append(lineEnd);

                        // build equation name 
                        String name = n.name;
                        String basename = name;
                        for (int i = 0; _dict.ContainsKey(eqnameleft + name + eqdots); i++)
                            name = basename + "_" + i;
                        name = eqnameleft + name;

                        // add to equations and models list 
                        if (_gams_syntax)
                        {
                            _equations.Append(string.Format("    {0} mass balance constraint {1}" + _up_sw.NewLine, name, n.number));
                            _models.Append(string.Format(",{0}    {1}", _up_sw.NewLine, name));
                        }

                        // add to the dictionary 
                        _dict.Add(name + eqdots, _constraints.ToString());
                    }

                    // end of equations and models strings 
                    if (_gams_syntax)
                    {
                        _equations.Append(";");
                        _models.Append("/;");
                    }
                }

                // add iteration and clear los and his
                _up_sw.Write(_model.mInfo.Iteration.ToString() + "\t" + f.ToString());
                _fxns.Add(f);
                _lastLos.Clear();
                _lastHis.Clear();

                // iterate through all links
                StringBuilder s_lo = new StringBuilder();
                StringBuilder s_cost = new StringBuilder();
                StringBuilder s_hi = new StringBuilder();
                for (int i = 0; i < this.variables.Length; i++)
                {
                    variable = this.variables[i];
                    if (!int.TryParse(variable.Replace("q_", ""), out num))
                        _model.FireOnError("Could not parse {0} to a number!");
                    l = _model.Links_All[num - 1];
                    _lastLos.Add(l.mlInfo.lo / _model.ScaleFactor);
                    _lastHis.Add(l.mlInfo.hi / _model.ScaleFactor);
                    s_lo.Append("\t" + l.mlInfo.lo.ToString());
                    s_cost.Append("\t" + l.mlInfo.cost.ToString());
                    s_hi.Append("\t" + l.mlInfo.hi.ToString());
                }
                _up_sw.Write(s_lo.ToString() + s_cost.ToString() + s_hi.ToString());

                // write the end 
                _up_sw.WriteLine();
            }
            else if (_model.mInfo.CurrentModelTimeStepIndex > 0 && _up_sw != null)
            {
                if (_gams_syntax)
                {
                    _up_sw.WriteLine(_equations.ToString());
                    string objfxn = _fxns[_fxns.Count - 1].ToString().Replace("^", "**");
                    foreach (string var in _fxns[_fxns.Count - 1].Variables())
                        objfxn = objfxn.Replace(var, string.Format("q('{0}')", var));
                    _up_sw.WriteLine("eq_obj..  f =e= " + objfxn + ";");
                }
                foreach (System.Collections.Generic.KeyValuePair<String, String> keyvalue in _dict)
                    _up_sw.WriteLine(keyvalue.Key + "\t" + keyvalue.Value);
                if (_gams_syntax)
                {
                    _up_sw.WriteLine(_models.ToString());
                    for (int i = 0; i < _lastLos.Count; i++)
                        if (_lastLos[i] == _lastHis[i])
                            _up_sw.WriteLine(string.Format("q.fx('q_{0}') = {1};", i + 1, _lastLos[i]));
                }
                _up_sw.Close();
                _up_sw = null;
            }
#endif
            return f + extra;
        }
    }
}
